@file:OptIn(ExperimentalComposeUiApi::class)

package com.fanketly.musicdiary.test

import androidx.compose.animation.core.AnimationVector2D
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.layout.LookaheadLayout
import androidx.compose.ui.layout.LookaheadLayoutScope
import androidx.compose.ui.layout.MeasurePolicy
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.fanketly.musicdiary.R
import com.fanketly.musicdiary.ui.theme.TestTheme
import com.fanketly.musicdiary.ui.theme.White
import java.lang.Integer.max
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.VectorConverter
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Constraints
import kotlinx.coroutines.launch

/**
 * Version 1.3.0-alpha01 新添加
 *LookaheadLayout是一个运行前瞻测量和布局传递以确定布局的布局。紧接着，另一个测量和放置通道将开始，
 * 其中任何布局的测量和放置都可以通过LookaheadLayoutScope.intermediateLayout根据前瞻结果进行调整。
在前瞻过程中，将跳过LookaheadLayoutScope.intermediateLayout中定义的布局调整逻辑，因此在预先确定目标布局时不会考虑布局的任何瞬态变形。
前瞻完成后，将开始另一次测量和布局传递。 LookaheadLayoutScope.intermediateLayout可用于根据传入约束和前瞻结果创建中间布局。
这可能会导致布局逐渐改变其大小和位置，使其朝向由前瞻计算的目标布局。
 **/
@Preview(showBackground = true)
@Composable
fun LookaheadLayoutPreview() {
    TestTheme {
        Surface(color = White) {
//            SimpleLookaheadLayout()
            SimpleLookaheadLayout2()
        }
    }
}

// 创建一个自定义修改器，为约束设置动画并使用它们测量子项。它建立在 `Modifier.intermediateLayout` 之上，允许访问布局的目标大小。
// 将创建调整大小动画以动画到目标大小。
// 基于动画值创建的固定约束将用于测量 childchild，以便所有孩子逐渐改变其大小以适应动画约束。
fun Modifier.animateConstraints(lookaheadScope: LookaheadLayoutScope) = composed {
    var sizeAnimation: Animatable<IntSize, AnimationVector2D>? by remember {
        mutableStateOf(null)
    }
    var targetSize: IntSize? by remember { mutableStateOf(null) }
    // 创建一个 `LaunchEffect` 来处理目标大小的变化。这避免了在测量布局阶段产生副作用。
    LaunchedEffect(Unit) {
        snapshotFlow { targetSize }.collect { target ->
            if (target != null && target != sizeAnimation?.targetValue) {
                sizeAnimation?.run {
                    launch { animateTo(target) }
                } ?: Animatable(target, IntSize.VectorConverter).let {
                    sizeAnimation = it
                }
            }
        }
    }
    with(lookaheadScope) {
        // `intermediateLayout` 中的度量逻辑在前瞻通道中被跳过，因为intermediateLayout 预计会产生布局转换的中间阶段。
        // 当在前瞻传递之后调用测量块时，子的前瞻大小将作为测量块的参数访问
        this@composed.intermediateLayout { measurable, _, lookaheadSize ->
            //当布局更改时，前瞻通道将为子修饰符计算新的最终大小。
            // 这个前瞻尺寸可用于动画尺寸变化，使动画从当前尺寸开始，逐渐向`lookaheadSize`变化。
            targetSize = lookaheadSize
            // 如果设置了动画，则读取动画大小。否则（即第一帧），使用不带动画的前瞻尺寸。
            val (width, height) = sizeAnimation?.value ?: lookaheadSize
            //使用动画大小创建一组固定的约束
            val animatedConstraints = Constraints.fixed(width, height)
            // 使用动画约束测量 childchild。
            val placeable = measurable.measure(animatedConstraints)
            layout(placeable.width, placeable.height) {
                placeable.place(0, 0)
            }
        }
    }
}

@Composable
private fun SimpleLookaheadLayout2() {
    LookaheadLayout(
        content = {
            var fullWidth by remember { mutableStateOf(false) }
            Row(
                (if (fullWidth) Modifier.fillMaxWidth() else Modifier.width(100.dp))
                    .height(200.dp)
                    //使用上面创建的自定义修改器为传递给子级的约束设置动画，从而在动画中调整子级的大小。
                    .animateConstraints(this@LookaheadLayout)
                    .clickable { fullWidth = !fullWidth }) {
                Box(
                    Modifier
                        .weight(1f)
                        .fillMaxHeight()
                        .background(Color.Red)
                )
                Box(
                    Modifier
                        .weight(2f)
                        .fillMaxHeight()
                        .background(Color.Yellow)
                )
            }
        }
    ) { measurables, constraints ->
        val placeables = measurables.map { it.measure(constraints) }
        val maxWidth: Int = placeables.maxOf { it.width }
        val maxHeight = placeables.maxOf { it.height }
        // 给孩子定位。
        layout(maxWidth, maxHeight) {
            placeables.forEach {
                it.place(0, 0)
            }
        }
    }

}

@Composable
fun SimpleLookaheadLayout() {

    var isCentered by remember { mutableStateOf(true) }
    val widthModifier = if (isCentered) Modifier.width(256.dp) else Modifier.fillMaxWidth()

    LookaheadLayout(content = {
        Box(Modifier.fillMaxSize()) {
            val placementModifier = if (isCentered) Modifier.align(Alignment.Center) else Modifier

            Column(
                widthModifier
                    .then(placementModifier)
                    .animateConstraintsAndPlacement()
                    .clickable(
                        onClick = { isCentered = !isCentered },
                        indication = null,
                        interactionSource = remember { MutableInteractionSource() },
                    )
            ) {
                AsyncImage(
                    modifier = Modifier
                        .fillMaxWidth()
                        .aspectRatio(1920f / 1080f),
                    model = R.drawable.diary_logo,
                    contentDescription = "character.name"
                )

                Text("character.name", style = MaterialTheme.typography.h3)
            }
        }

    }, modifier = Modifier, measurePolicy = { measurables, constraints ->
        val placeables = measurables.map { it.measure(constraints) }
        val maxWidth = placeables.maxOf { it.width }
        val maxHeight = placeables.maxOf { it.height }

        layout(maxWidth, maxHeight) {
            placeables.forEach {
                it.place(0, 0)
            }
        }
    })
}

internal val internalMeasurePolicy = MeasurePolicy { measurables, constraints ->
    val contentConstraints = constraints.copy(minWidth = 0, minHeight = 0)
    val placeables = measurables.map { it.measure(contentConstraints) }
    val maxWidth: Int = max(placeables.maxOf { it.width }, constraints.minWidth)
    val maxHeight = max(placeables.maxOf { it.height }, constraints.minHeight)
    // Position the children.
    layout(maxWidth, maxHeight) {
        placeables.forEach {
            it.place(0, 0)
        }
    }
}